package masterjava.common.dao.hibernate;

import masterjava.common.dao.DaoContext;
import masterjava.common.dao.DaoContextHolder;
import masterjava.common.logging.ServerLogger;
import masterjava.common.shared.entity.GenericEntity;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.support.DataAccessUtils;

import java.io.Serializable;
import java.util.List;

/**
 * User: Grigory Kislin
 * Date: 27/06/11
 */
public class HibernateDaoHelper {

    private static final ServerLogger LOGGER = ServerLogger.get(HibernateDaoHelper.class);

    @Qualifier(value = "mySessionFactory")
    private SessionFactory sessionFactory;

    private DaoContextHolder daoContextHolder;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    public DaoContextHolder getDaoContextHolder() {
        return daoContextHolder;
    }

    public void setDaoContextHolder(DaoContextHolder daoContextHolder) {
        this.daoContextHolder = daoContextHolder;
    }

    public <T extends GenericEntity> Criteria getRootCriteria(Class<T> persistentClass) {
        Criteria criteria = getSession().createCriteria(persistentClass);
        return criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
    }


    public <T extends GenericEntity> List<T> loadAll(Class<T> persistentClass) {
        LOGGER.debug("loadAll " + persistentClass.getName());
        return list(getRootCriteria(persistentClass));
    }

    @SuppressWarnings("unchecked")
    public <T extends GenericEntity> List<T> list(Criteria c) {
        List<T> list = prepareCriteria(c).list();
        return prepareToSerialize(list);
    }

    @SuppressWarnings("unchecked")
    public <T extends GenericEntity> List<T> list(Query q) {
        List<T> list = prepareQuery(q).list();
        return prepareToSerialize(list);
    }

    @SuppressWarnings("unchecked")
    /* return initialized entity */
    public <T extends GenericEntity, ID extends Serializable> T get(Class<T> persistentClass, ID id) {
        LOGGER.debug("get " + persistentClass.getName() + ":" + id);
        T entity = (T) getSession().get(persistentClass, id);
        return prepareToSerialize(entity);
    }

    @SuppressWarnings("unchecked")
    /* return uninitialized entity */
    public <T extends GenericEntity, ID extends Serializable> T load(Class<T> persistentClass, ID id) {
        return (T) getSession().load(persistentClass, id);
    }

    @SuppressWarnings("unchecked")
    public Serializable saveOrUpdate(GenericEntity entity) {
        LOGGER.debug("saveOrUpdate " + entity.toString());
        getSession().saveOrUpdate(prepareToPersist(entity));
        return entity.getId();
    }

    public void delete(Class<? extends GenericEntity> persistentClass, Serializable id) {
        delete((GenericEntity) getSession().load(persistentClass, id));
    }

    public void delete(GenericEntity entity) {
        LOGGER.debug("delete " + entity.toString());
        getSession().delete(entity);
    }

    public <T extends GenericEntity> List<T> findByNamedQueryAndNamedParam(String queryName, String paramName, Serializable value) {
        return findByNamedQueryAndNamedParam(queryName, new String[]{paramName}, new Serializable[]{value});
    }

    public GenericEntity findUnique(String queryName, String paramName, Serializable value) {
        return findUnique(queryName, new String[]{paramName}, new Serializable[]{value});
    }

    public GenericEntity findUnique(String queryName, String[] paramNames, Serializable[] values) {
        List<? extends GenericEntity> list = findByNamedQueryAndNamedParam(queryName, paramNames, values);
        return DataAccessUtils.uniqueResult(list);
    }

    public <T extends GenericEntity> List<T> findByNamedQueryAndNamedParam(final String queryName, final String[] paramNames, final Serializable[] values) {
        LOGGER.debug("findByNamedQueryAndNamedParam (queryName:" + queryName + ")");
        Query query = getSession().getNamedQuery(queryName);
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                applyNamedParameterToQuery(query, paramNames[i], values[i]);
            }
        }
        return list(query);
    }

    protected void applyNamedParameterToQuery(Query queryObject, String paramName, Object value) {
        if (value instanceof List) {
            queryObject.setParameterList(paramName, (List) value);
        } else if (value instanceof Object[]) {
            queryObject.setParameterList(paramName, (Object[]) value);
        } else {
            queryObject.setParameter(paramName, value);
        }
    }

    protected Criteria prepareCriteria(Criteria c) {
        DaoContext ctx = daoContextHolder.getDaoContext();
        return (ctx != null) ? ctx.prepareCriteria(c) : c;
    }

    protected Query prepareQuery(Query q) {
        DaoContext ctx = daoContextHolder.getDaoContext();
        return (ctx != null) ? ctx.prepareQuery(q) : q;
    }

    protected <T extends GenericEntity> T prepareToPersist(T entity) {
        entity.prepareToPersist();
        return entity;
    }

    protected <T extends GenericEntity> T prepareToSerialize(T entity) {
        entity.prepareToSerialize();
        return entity;
    }

    protected <T extends GenericEntity> List<T> prepareToSerialize(List<T> list) {
        for (T entity : list) {
            prepareToSerialize(entity);
        }
        return list;
    }
}
